1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-04 15:02:09 +03:00

Support CLUSTER commands (#2015)

* Support CLUSTER commands

* add some client tests

* remove only

* delete cluster slaves

* delete reset clietn test

* SET SLOT

* test with client

* fix fail

* Update CLUSTER_COUNTKEYSINSLOT.spec.ts

* move commands to client/commands.ts

* clusterNode

* remove CLUSTER-SET-CONFIG-EPOCH test with client

* clean code

Co-authored-by: leibale <leibale1998@gmail.com>
This commit is contained in:
Avital Fine
2022-03-29 00:31:15 +02:00
committed by GitHub
parent 5ade5dadc0
commit 5821fcbe4d
37 changed files with 393 additions and 29 deletions

View File

@@ -1,13 +1,11 @@
export function transformArguments(slots: number | Array<number>): Array<string> {
const args = ['CLUSTER', 'ADDSLOTS'];
import { RedisCommandArguments } from '.';
import { pushVerdictNumberArguments } from './generic-transformers';
if (typeof slots === 'number') {
args.push(slots.toString());
} else {
args.push(...slots.map(String));
}
return args;
export function transformArguments(slots: number | Array<number>): RedisCommandArguments {
return pushVerdictNumberArguments(
['CLUSTER', 'ADDSLOTS'],
slots
);
}
export declare function transformReply(): string;

View File

@@ -0,0 +1,19 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './CLUSTER_BUMPEPOCH';
describe('CLUSTER BUMPEPOCH', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['CLUSTER', 'BUMPEPOCH']
);
});
testUtils.testWithCluster('clusterNode.clusterBumpEpoch', async cluster => {
assert.equal(
typeof await cluster.getSlotMaster(0).client.clusterBumpEpoch(),
'string'
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,5 @@
export function transformArguments(): Array<string> {
return ['CLUSTER', 'BUMPEPOCH'];
}
export declare function transformReply(): 'BUMPED' | 'STILL';

View File

@@ -0,0 +1,22 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './CLUSTER_COUNT-FAILURE-REPORTS';
describe('CLUSTER COUNT-FAILURE-REPORTS', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('0'),
['CLUSTER', 'COUNT-FAILURE-REPORTS', '0']
);
});
testUtils.testWithCluster('clusterNode.clusterCountFailureReports', async cluster => {
const { client } = cluster.getSlotMaster(0);
assert.equal(
typeof await client.clusterCountFailureReports(
await client.clusterMyId()
),
'number'
);
}, GLOBAL.CLUSTERS.OPEN);
});

View File

@@ -0,0 +1,5 @@
export function transformArguments(nodeId: string): Array<string> {
return ['CLUSTER', 'COUNT-FAILURE-REPORTS', nodeId];
}
export declare function transformReply(): number;

View File

@@ -0,0 +1,19 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './CLUSTER_COUNTKEYSINSLOT';
describe('CLUSTER COUNTKEYSINSLOT', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(0),
['CLUSTER', 'COUNTKEYSINSLOT', '0']
);
});
testUtils.testWithCluster('clusterNode.clusterCountKeysInSlot', async cluster => {
assert.equal(
typeof await cluster.getSlotMaster(0).client.clusterCountKeysInSlot(0),
'number'
);
}, GLOBAL.CLUSTERS.OPEN);
});

View File

@@ -0,0 +1,5 @@
export function transformArguments(slot: number): Array<string> {
return ['CLUSTER', 'COUNTKEYSINSLOT', slot.toString()];
}
export declare function transformReply(): number;

View File

@@ -0,0 +1,20 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CLUSTER_DELSLOTS';
describe('CLUSTER DELSLOTS', () => {
describe('transformArguments', () => {
it('single', () => {
assert.deepEqual(
transformArguments(0),
['CLUSTER', 'DELSLOTS', '0']
);
});
it('multiple', () => {
assert.deepEqual(
transformArguments([0, 1]),
['CLUSTER', 'DELSLOTS', '0', '1']
);
});
});
});

View File

@@ -0,0 +1,11 @@
import { RedisCommandArguments } from '.';
import { pushVerdictNumberArguments } from './generic-transformers';
export function transformArguments(slots: number | Array<number>): RedisCommandArguments {
return pushVerdictNumberArguments(
['CLUSTER', 'DELSLOTS'],
slots
);
}
export declare function transformReply(): 'OK';

View File

@@ -0,0 +1,20 @@
import { strict as assert } from 'assert';
import { FailoverModes, transformArguments } from './CLUSTER_FAILOVER';
describe('CLUSTER FAILOVER', () => {
describe('transformArguments', () => {
it('simple', () => {
assert.deepEqual(
transformArguments(),
['CLUSTER', 'FAILOVER']
);
});
it('with mode', () => {
assert.deepEqual(
transformArguments(FailoverModes.FORCE),
['CLUSTER', 'FAILOVER', 'FORCE']
);
});
});
});

View File

@@ -0,0 +1,16 @@
export enum FailoverModes {
FORCE = 'FORCE',
TAKEOVER = 'TAKEOVER'
}
export function transformArguments(mode?: FailoverModes): Array<string> {
const args = ['CLUSTER', 'FAILOVER'];
if (mode) {
args.push(mode);
}
return args;
}
export declare function transformReply(): 'OK';

View File

@@ -2,4 +2,4 @@ export function transformArguments(): Array<string> {
return ['CLUSTER', 'FLUSHSLOTS'];
}
export declare function transformReply(): string;
export declare function transformReply(): 'OK';

View File

@@ -0,0 +1,11 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CLUSTER_FORGET';
describe('CLUSTER FORGET', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('0'),
['CLUSTER', 'FORGET', '0']
);
});
});

View File

@@ -0,0 +1,5 @@
export function transformArguments(nodeId: string): Array<string> {
return ['CLUSTER', 'FORGET', nodeId];
}
export declare function transformReply(): 'OK';

View File

@@ -1,4 +1,5 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './CLUSTER_GETKEYSINSLOT';
describe('CLUSTER GETKEYSINSLOT', () => {
@@ -8,4 +9,12 @@ describe('CLUSTER GETKEYSINSLOT', () => {
['CLUSTER', 'GETKEYSINSLOT', '0', '10']
);
});
testUtils.testWithCluster('clusterNode.clusterGetKeysInSlot', async cluster => {
const reply = await cluster.getSlotMaster(0).client.clusterGetKeysInSlot(0, 1);
assert.ok(Array.isArray(reply));
for (const item of reply) {
assert.equal(typeof item, 'string');
}
}, GLOBAL.CLUSTERS.OPEN);
});

View File

@@ -2,4 +2,4 @@ export function transformArguments(slot: number, count: number): Array<string> {
return ['CLUSTER', 'GETKEYSINSLOT', slot.toString(), count.toString()];
}
export declare function transformReply(): string;
export declare function transformReply(): Array<string>;

View File

@@ -1,4 +1,5 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments, transformReply } from './CLUSTER_INFO';
describe('CLUSTER INFO', () => {
@@ -43,4 +44,11 @@ describe('CLUSTER INFO', () => {
}
);
});
testUtils.testWithCluster('clusterNode.clusterInfo', async cluster => {
assert.notEqual(
await cluster.getSlotMaster(0).client.clusterInfo(),
null
);
}, GLOBAL.CLUSTERS.OPEN);
});

View File

@@ -0,0 +1,19 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './CLUSTER_KEYSLOT';
describe('CLUSTER KEYSLOT', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('key'),
['CLUSTER', 'KEYSLOT', 'key']
);
});
testUtils.testWithCluster('clusterNode.clusterKeySlot', async cluster => {
assert.equal(
typeof await cluster.getSlotMaster(0).client.clusterKeySlot('key'),
'number'
);
}, GLOBAL.CLUSTERS.OPEN);
});

View File

@@ -0,0 +1,5 @@
export function transformArguments(key: string): Array<string> {
return ['CLUSTER', 'KEYSLOT', key];
}
export declare function transformReply(): number;

View File

@@ -2,4 +2,4 @@ export function transformArguments(ip: string, port: number): Array<string> {
return ['CLUSTER', 'MEET', ip, port.toString()];
}
export declare function transformReply(): string;
export declare function transformReply(): 'OK';

View File

@@ -0,0 +1,19 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './CLUSTER_MYID';
describe('CLUSTER MYID', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['CLUSTER', 'MYID']
);
});
testUtils.testWithCluster('clusterNode.clusterMyId', async cluster => {
assert.equal(
typeof await cluster.getSlotMaster(0).client.clusterMyId(),
'string'
);
}, GLOBAL.CLUSTERS.OPEN);
});

View File

@@ -0,0 +1,5 @@
export function transformArguments(): Array<string> {
return ['CLUSTER', 'MYID'];
}
export declare function transformReply(): string;

View File

@@ -0,0 +1,11 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CLUSTER_REPLICAS';
describe('CLUSTER REPLICAS', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('0'),
['CLUSTER', 'REPLICAS', '0']
);
});
});

View File

@@ -0,0 +1,5 @@
export function transformArguments(nodeId: string): Array<string> {
return ['CLUSTER', 'REPLICAS', nodeId];
}
export { transformReply } from './CLUSTER_NODES';

View File

@@ -0,0 +1,11 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CLUSTER_REPLICATE';
describe('CLUSTER REPLICATE', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('0'),
['CLUSTER', 'REPLICATE', '0']
);
});
});

View File

@@ -0,0 +1,5 @@
export function transformArguments(nodeId: string): Array<string> {
return ['CLUSTER', 'REPLICATE', nodeId];
}
export declare function transformReply(): 'OK';

View File

@@ -10,18 +10,11 @@ describe('CLUSTER RESET', () => {
);
});
it('HARD', () => {
it('with mode', () => {
assert.deepEqual(
transformArguments('HARD'),
['CLUSTER', 'RESET', 'HARD']
);
});
it('SOFT', () => {
assert.deepEqual(
transformArguments('SOFT'),
['CLUSTER', 'RESET', 'SOFT']
);
});
});
});

View File

@@ -1,6 +1,4 @@
export type ClusterResetModes = 'HARD' | 'SOFT';
export function transformArguments(mode?: ClusterResetModes): Array<string> {
export function transformArguments(mode?: 'HARD' | 'SOFT'): Array<string> {
const args = ['CLUSTER', 'RESET'];
if (mode) {

View File

@@ -0,0 +1,19 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './CLUSTER_SAVECONFIG';
describe('CLUSTER SAVECONFIG', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['CLUSTER', 'SAVECONFIG']
);
});
testUtils.testWithCluster('clusterNode.clusterSaveConfig', async cluster => {
assert.equal(
await cluster.getSlotMaster(0).client.clusterSaveConfig(),
'OK'
);
}, GLOBAL.CLUSTERS.OPEN);
});

View File

@@ -0,0 +1,5 @@
export function transformArguments(): Array<string> {
return ['CLUSTER', 'SAVECONFIG'];
}
export declare function transformReply(): 'OK';

View File

@@ -0,0 +1,11 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CLUSTER_SET-CONFIG-EPOCH';
describe('CLUSTER SET-CONFIG-EPOCH', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(0),
['CLUSTER', 'SET-CONFIG-EPOCH', '0']
);
});
});

View File

@@ -0,0 +1,5 @@
export function transformArguments(configEpoch: number): Array<string> {
return ['CLUSTER', 'SET-CONFIG-EPOCH', configEpoch.toString()];
}
export declare function transformReply(): 'OK';

View File

@@ -5,7 +5,11 @@ export enum ClusterSlotStates {
NODE = 'NODE'
}
export function transformArguments(slot: number, state: ClusterSlotStates, nodeId?: string): Array<string> {
export function transformArguments(
slot: number,
state: ClusterSlotStates,
nodeId?: string
): Array<string> {
const args = ['CLUSTER', 'SETSLOT', slot.toString(), state];
if (nodeId) {
@@ -15,4 +19,4 @@ export function transformArguments(slot: number, state: ClusterSlotStates, nodeI
return args;
}
export declare function transformReply(): string;
export declare function transformReply(): 'OK';

View File

@@ -6,7 +6,12 @@ export function transformArguments(): RedisCommandArguments {
type ClusterSlotsRawNode = [ip: string, port: number, id: string];
type ClusterSlotsRawReply = Array<[from: number, to: number, master: ClusterSlotsRawNode, ...replicas: Array<ClusterSlotsRawNode>]>;
type ClusterSlotsRawReply = Array<[
from: number,
to: number,
master: ClusterSlotsRawNode,
...replicas: Array<ClusterSlotsRawNode>
]>;
type ClusterSlotsNode = {
ip: string;

View File

@@ -19,6 +19,7 @@ import {
transformPXAT,
pushEvalArguments,
pushVerdictArguments,
pushVerdictNumberArguments,
pushVerdictArgument,
pushOptionalVerdictArgument,
transformCommandReply,
@@ -579,6 +580,22 @@ describe('Generic Transformers', () => {
});
});
describe('pushVerdictNumberArguments', () => {
it('number', () => {
assert.deepEqual(
pushVerdictNumberArguments([], 0),
['0']
);
});
it('array', () => {
assert.deepEqual(
pushVerdictNumberArguments([], [0, 1]),
['0', '1']
);
});
});
describe('pushVerdictArgument', () => {
it('string', () => {
assert.deepEqual(

View File

@@ -322,6 +322,21 @@ export function pushVerdictArguments(args: RedisCommandArguments, value: RedisCo
return args;
}
export function pushVerdictNumberArguments(
args: RedisCommandArguments,
value: number | Array<number>
): RedisCommandArguments {
if (Array.isArray(value)) {
for (const item of value) {
args.push(item.toString());
}
} else {
args.push(value.toString());
}
return args;
}
export function pushVerdictArgument(
args: RedisCommandArguments,
value: RedisCommandArgument | Array<RedisCommandArgument>