You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-07 13:22:56 +03:00
CLUSERT [INFO|NODES|REPLICAS]
This commit is contained in:
@@ -10,46 +10,11 @@ describe('CLUSTER INFO', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('transformReply', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
transformReply([
|
|
||||||
'cluster_state:ok',
|
|
||||||
'cluster_slots_assigned:16384',
|
|
||||||
'cluster_slots_ok:16384',
|
|
||||||
'cluster_slots_pfail:0',
|
|
||||||
'cluster_slots_fail:0',
|
|
||||||
'cluster_known_nodes:6',
|
|
||||||
'cluster_size:3',
|
|
||||||
'cluster_current_epoch:6',
|
|
||||||
'cluster_my_epoch:2',
|
|
||||||
'cluster_stats_messages_sent:1483972',
|
|
||||||
'cluster_stats_messages_received:1483968'
|
|
||||||
].join('\r\n')),
|
|
||||||
{
|
|
||||||
state: 'ok',
|
|
||||||
slots: {
|
|
||||||
assigned: 16384,
|
|
||||||
ok: 16384,
|
|
||||||
pfail: 0,
|
|
||||||
fail: 0
|
|
||||||
},
|
|
||||||
knownNodes: 6,
|
|
||||||
size: 3,
|
|
||||||
currentEpoch: 6,
|
|
||||||
myEpoch: 2,
|
|
||||||
stats: {
|
|
||||||
messagesSent: 1483972,
|
|
||||||
messagesReceived: 1483968
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
testUtils.testWithCluster('clusterNode.clusterInfo', async cluster => {
|
testUtils.testWithCluster('clusterNode.clusterInfo', async cluster => {
|
||||||
const client = await cluster.nodeClient(cluster.masters[0]);
|
const client = await cluster.nodeClient(cluster.masters[0]);
|
||||||
assert.notEqual(
|
assert.equal(
|
||||||
await client.clusterInfo(),
|
typeof await client.clusterInfo(),
|
||||||
null
|
'string'
|
||||||
);
|
);
|
||||||
}, GLOBAL.CLUSTERS.OPEN);
|
}, GLOBAL.CLUSTERS.OPEN);
|
||||||
});
|
});
|
||||||
|
@@ -1,47 +1,10 @@
|
|||||||
export function transformArguments(): Array<string> {
|
import { VerbatimStringReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
FIRST_KEY_INDEX: undefined,
|
||||||
|
IS_READ_ONLY: true,
|
||||||
|
transformArguments() {
|
||||||
return ['CLUSTER', 'INFO'];
|
return ['CLUSTER', 'INFO'];
|
||||||
}
|
|
||||||
|
|
||||||
interface ClusterInfoReply {
|
|
||||||
state: string;
|
|
||||||
slots: {
|
|
||||||
assigned: number;
|
|
||||||
ok: number;
|
|
||||||
pfail: number;
|
|
||||||
fail: number;
|
|
||||||
};
|
|
||||||
knownNodes: number;
|
|
||||||
size: number;
|
|
||||||
currentEpoch: number;
|
|
||||||
myEpoch: number;
|
|
||||||
stats: {
|
|
||||||
messagesSent: number;
|
|
||||||
messagesReceived: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function transformReply(reply: string): ClusterInfoReply {
|
|
||||||
const lines = reply.split('\r\n');
|
|
||||||
|
|
||||||
return {
|
|
||||||
state: extractLineValue(lines[0]),
|
|
||||||
slots: {
|
|
||||||
assigned: Number(extractLineValue(lines[1])),
|
|
||||||
ok: Number(extractLineValue(lines[2])),
|
|
||||||
pfail: Number(extractLineValue(lines[3])),
|
|
||||||
fail: Number(extractLineValue(lines[4]))
|
|
||||||
},
|
},
|
||||||
knownNodes: Number(extractLineValue(lines[5])),
|
transformReply: undefined as unknown as () => VerbatimStringReply
|
||||||
size: Number(extractLineValue(lines[6])),
|
} as const satisfies Command;
|
||||||
currentEpoch: Number(extractLineValue(lines[7])),
|
|
||||||
myEpoch: Number(extractLineValue(lines[8])),
|
|
||||||
stats: {
|
|
||||||
messagesSent: Number(extractLineValue(lines[9])),
|
|
||||||
messagesReceived: Number(extractLineValue(lines[10]))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function extractLineValue(line: string): string {
|
|
||||||
return line.substring(line.indexOf(':') + 1);
|
|
||||||
}
|
|
||||||
|
@@ -1,145 +1,20 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import { RedisClusterNodeLinkStates, transformArguments, transformReply } from './CLUSTER_NODES';
|
import testUtils, { GLOBAL } from '../test-utils';
|
||||||
|
import CLUSTER_NODES from './CLUSTER_NODES';
|
||||||
|
|
||||||
describe('CLUSTER NODES', () => {
|
describe('CLUSTER NODES', () => {
|
||||||
it('transformArguments', () => {
|
it('transformArguments', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformArguments(),
|
CLUSTER_NODES.transformArguments(),
|
||||||
['CLUSTER', 'NODES']
|
['CLUSTER', 'NODES']
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('transformReply', () => {
|
testUtils.testWithCluster('clusterNode.clusterNodes', async cluster => {
|
||||||
it('simple', () => {
|
const client = await cluster.nodeClient(cluster.masters[0]);
|
||||||
assert.deepEqual(
|
assert.equal(
|
||||||
transformReply([
|
typeof await client.clusterNodes(),
|
||||||
'master 127.0.0.1:30001@31001 myself,master - 0 0 1 connected 0-16384',
|
'string'
|
||||||
'slave 127.0.0.1:30002@31002 slave master 0 0 1 connected',
|
|
||||||
''
|
|
||||||
].join('\n')),
|
|
||||||
[{
|
|
||||||
id: 'master',
|
|
||||||
address: '127.0.0.1:30001@31001',
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: 30001,
|
|
||||||
cport: 31001,
|
|
||||||
flags: ['myself', 'master'],
|
|
||||||
pingSent: 0,
|
|
||||||
pongRecv: 0,
|
|
||||||
configEpoch: 1,
|
|
||||||
linkState: RedisClusterNodeLinkStates.CONNECTED,
|
|
||||||
slots: [{
|
|
||||||
from: 0,
|
|
||||||
to: 16384
|
|
||||||
}],
|
|
||||||
replicas: [{
|
|
||||||
id: 'slave',
|
|
||||||
address: '127.0.0.1:30002@31002',
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: 30002,
|
|
||||||
cport: 31002,
|
|
||||||
flags: ['slave'],
|
|
||||||
pingSent: 0,
|
|
||||||
pongRecv: 0,
|
|
||||||
configEpoch: 1,
|
|
||||||
linkState: RedisClusterNodeLinkStates.CONNECTED
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
);
|
);
|
||||||
});
|
}, GLOBAL.CLUSTERS.OPEN);
|
||||||
|
|
||||||
it('should support addresses without cport', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
transformReply(
|
|
||||||
'id 127.0.0.1:30001 master - 0 0 0 connected 0-16384\n'
|
|
||||||
),
|
|
||||||
[{
|
|
||||||
id: 'id',
|
|
||||||
address: '127.0.0.1:30001',
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: 30001,
|
|
||||||
cport: null,
|
|
||||||
flags: ['master'],
|
|
||||||
pingSent: 0,
|
|
||||||
pongRecv: 0,
|
|
||||||
configEpoch: 0,
|
|
||||||
linkState: RedisClusterNodeLinkStates.CONNECTED,
|
|
||||||
slots: [{
|
|
||||||
from: 0,
|
|
||||||
to: 16384
|
|
||||||
}],
|
|
||||||
replicas: []
|
|
||||||
}]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support ipv6 addresses', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
transformReply(
|
|
||||||
'id 2a02:6b8:c21:330d:0:1589:ebbe:b1a0:6379@16379 master - 0 0 0 connected 0-549\n'
|
|
||||||
),
|
|
||||||
[{
|
|
||||||
id: 'id',
|
|
||||||
address: '2a02:6b8:c21:330d:0:1589:ebbe:b1a0:6379@16379',
|
|
||||||
host: '2a02:6b8:c21:330d:0:1589:ebbe:b1a0',
|
|
||||||
port: 6379,
|
|
||||||
cport: 16379,
|
|
||||||
flags: ['master'],
|
|
||||||
pingSent: 0,
|
|
||||||
pongRecv: 0,
|
|
||||||
configEpoch: 0,
|
|
||||||
linkState: RedisClusterNodeLinkStates.CONNECTED,
|
|
||||||
slots: [{
|
|
||||||
from: 0,
|
|
||||||
to: 549
|
|
||||||
}],
|
|
||||||
replicas: []
|
|
||||||
}]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.skip('with importing slots', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
transformReply(
|
|
||||||
'id 127.0.0.1:30001@31001 master - 0 0 0 connected 0-<-16384\n'
|
|
||||||
),
|
|
||||||
[{
|
|
||||||
id: 'id',
|
|
||||||
address: '127.0.0.1:30001@31001',
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: 30001,
|
|
||||||
cport: 31001,
|
|
||||||
flags: ['master'],
|
|
||||||
pingSent: 0,
|
|
||||||
pongRecv: 0,
|
|
||||||
configEpoch: 0,
|
|
||||||
linkState: RedisClusterNodeLinkStates.CONNECTED,
|
|
||||||
slots: [], // TODO
|
|
||||||
replicas: []
|
|
||||||
}]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.skip('with migrating slots', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
transformReply(
|
|
||||||
'id 127.0.0.1:30001@31001 master - 0 0 0 connected 0->-16384\n'
|
|
||||||
),
|
|
||||||
[{
|
|
||||||
id: 'id',
|
|
||||||
address: '127.0.0.1:30001@31001',
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: 30001,
|
|
||||||
cport: 31001,
|
|
||||||
flags: ['master'],
|
|
||||||
pingSent: 0,
|
|
||||||
pongRecv: 0,
|
|
||||||
configEpoch: 0,
|
|
||||||
linkState: RedisClusterNodeLinkStates.CONNECTED,
|
|
||||||
slots: [], // TODO
|
|
||||||
replicas: []
|
|
||||||
}]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@@ -1,105 +1,10 @@
|
|||||||
export function transformArguments(): Array<string> {
|
import { VerbatimStringReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
FIRST_KEY_INDEX: undefined,
|
||||||
|
IS_READ_ONLY: true,
|
||||||
|
transformArguments() {
|
||||||
return ['CLUSTER', 'NODES'];
|
return ['CLUSTER', 'NODES'];
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => VerbatimStringReply
|
||||||
export enum RedisClusterNodeLinkStates {
|
} as const satisfies Command;
|
||||||
CONNECTED = 'connected',
|
|
||||||
DISCONNECTED = 'disconnected'
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RedisClusterNodeAddress {
|
|
||||||
host: string;
|
|
||||||
port: number;
|
|
||||||
cport: number | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RedisClusterReplicaNode extends RedisClusterNodeAddress {
|
|
||||||
id: string;
|
|
||||||
address: string;
|
|
||||||
flags: Array<string>;
|
|
||||||
pingSent: number;
|
|
||||||
pongRecv: number;
|
|
||||||
configEpoch: number;
|
|
||||||
linkState: RedisClusterNodeLinkStates;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RedisClusterMasterNode extends RedisClusterReplicaNode {
|
|
||||||
slots: Array<{
|
|
||||||
from: number;
|
|
||||||
to: number;
|
|
||||||
}>;
|
|
||||||
replicas: Array<RedisClusterReplicaNode>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function transformReply(reply: string): Array<RedisClusterMasterNode> {
|
|
||||||
const lines = reply.split('\n');
|
|
||||||
lines.pop(); // last line is empty
|
|
||||||
|
|
||||||
const mastersMap = new Map<string, RedisClusterMasterNode>(),
|
|
||||||
replicasMap = new Map<string, Array<RedisClusterReplicaNode>>();
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
const [id, address, flags, masterId, pingSent, pongRecv, configEpoch, linkState, ...slots] = line.split(' '),
|
|
||||||
node = {
|
|
||||||
id,
|
|
||||||
address,
|
|
||||||
...transformNodeAddress(address),
|
|
||||||
flags: flags.split(','),
|
|
||||||
pingSent: Number(pingSent),
|
|
||||||
pongRecv: Number(pongRecv),
|
|
||||||
configEpoch: Number(configEpoch),
|
|
||||||
linkState: (linkState as RedisClusterNodeLinkStates)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (masterId === '-') {
|
|
||||||
let replicas = replicasMap.get(id);
|
|
||||||
if (!replicas) {
|
|
||||||
replicas = [];
|
|
||||||
replicasMap.set(id, replicas);
|
|
||||||
}
|
|
||||||
|
|
||||||
mastersMap.set(id, {
|
|
||||||
...node,
|
|
||||||
slots: slots.map(slot => {
|
|
||||||
// TODO: importing & exporting (https://redis.io/commands/cluster-nodes#special-slot-entries)
|
|
||||||
const [fromString, toString] = slot.split('-', 2),
|
|
||||||
from = Number(fromString);
|
|
||||||
return {
|
|
||||||
from,
|
|
||||||
to: toString ? Number(toString) : from
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
replicas
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const replicas = replicasMap.get(masterId);
|
|
||||||
if (!replicas) {
|
|
||||||
replicasMap.set(masterId, [node]);
|
|
||||||
} else {
|
|
||||||
replicas.push(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [...mastersMap.values()];
|
|
||||||
}
|
|
||||||
|
|
||||||
function transformNodeAddress(address: string): RedisClusterNodeAddress {
|
|
||||||
const indexOfColon = address.lastIndexOf(':'),
|
|
||||||
indexOfAt = address.indexOf('@', indexOfColon),
|
|
||||||
host = address.substring(0, indexOfColon);
|
|
||||||
|
|
||||||
if (indexOfAt === -1) {
|
|
||||||
return {
|
|
||||||
host,
|
|
||||||
port: Number(address.substring(indexOfColon + 1)),
|
|
||||||
cport: null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
host: address.substring(0, indexOfColon),
|
|
||||||
port: Number(address.substring(indexOfColon + 1, indexOfAt)),
|
|
||||||
cport: Number(address.substring(indexOfAt + 1))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
@@ -1,11 +1,20 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import { transformArguments } from './CLUSTER_REPLICAS';
|
import testUtils, { GLOBAL } from '../test-utils';
|
||||||
|
import CLUSTER_REPLICAS from './CLUSTER_REPLICAS';
|
||||||
|
|
||||||
describe('CLUSTER REPLICAS', () => {
|
describe('CLUSTER REPLICAS', () => {
|
||||||
it('transformArguments', () => {
|
it('transformArguments', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformArguments('0'),
|
CLUSTER_REPLICAS.transformArguments('0'),
|
||||||
['CLUSTER', 'REPLICAS', '0']
|
['CLUSTER', 'REPLICAS', '0']
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testUtils.testWithCluster('clusterNode.clusterReplicas', async cluster => {
|
||||||
|
const client = await cluster.nodeClient(cluster.masters[0]);
|
||||||
|
assert.equal(
|
||||||
|
typeof await client.clusterReplicas(cluster.masters[0].id),
|
||||||
|
'string'
|
||||||
|
);
|
||||||
|
}, GLOBAL.CLUSTERS.OPEN);
|
||||||
});
|
});
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
export function transformArguments(nodeId: string): Array<string> {
|
import { RedisArgument, VerbatimStringReply, Command } from '@redis/client/dist/lib/RESP/types';
|
||||||
return ['CLUSTER', 'REPLICAS', nodeId];
|
|
||||||
}
|
|
||||||
|
|
||||||
export { transformReply } from './CLUSTER_NODES';
|
export default {
|
||||||
|
FIRST_KEY_INDEX: undefined,
|
||||||
|
IS_READ_ONLY: true,
|
||||||
|
transformArguments(nodeId: RedisArgument) {
|
||||||
|
return ['CLUSTER', 'REPLICAS', nodeId];
|
||||||
|
},
|
||||||
|
transformReply: undefined as unknown as () => VerbatimStringReply
|
||||||
|
} as const satisfies Command;
|
||||||
|
@@ -54,13 +54,13 @@ import CLUSTER_FAILOVER from './CLUSTER_FAILOVER';
|
|||||||
import CLUSTER_FLUSHSLOTS from './CLUSTER_FLUSHSLOTS';
|
import CLUSTER_FLUSHSLOTS from './CLUSTER_FLUSHSLOTS';
|
||||||
import CLUSTER_FORGET from './CLUSTER_FORGET';
|
import CLUSTER_FORGET from './CLUSTER_FORGET';
|
||||||
import CLUSTER_GETKEYSINSLOT from './CLUSTER_GETKEYSINSLOT';
|
import CLUSTER_GETKEYSINSLOT from './CLUSTER_GETKEYSINSLOT';
|
||||||
// import CLUSTER_INFO from './CLUSTER_INFO';
|
import CLUSTER_INFO from './CLUSTER_INFO';
|
||||||
import CLUSTER_KEYSLOT from './CLUSTER_KEYSLOT';
|
import CLUSTER_KEYSLOT from './CLUSTER_KEYSLOT';
|
||||||
import CLUSTER_LINKS from './CLUSTER_LINKS';
|
import CLUSTER_LINKS from './CLUSTER_LINKS';
|
||||||
import CLUSTER_MEET from './CLUSTER_MEET';
|
import CLUSTER_MEET from './CLUSTER_MEET';
|
||||||
import CLUSTER_MYID from './CLUSTER_MYID';
|
import CLUSTER_MYID from './CLUSTER_MYID';
|
||||||
// import CLUSTER_NODES from './CLUSTER_NODES';
|
import CLUSTER_NODES from './CLUSTER_NODES';
|
||||||
// import CLUSTER_REPLICAS from './CLUSTER_REPLICAS';
|
import CLUSTER_REPLICAS from './CLUSTER_REPLICAS';
|
||||||
import CLUSTER_REPLICATE from './CLUSTER_REPLICATE';
|
import CLUSTER_REPLICATE from './CLUSTER_REPLICATE';
|
||||||
import CLUSTER_RESET from './CLUSTER_RESET';
|
import CLUSTER_RESET from './CLUSTER_RESET';
|
||||||
import CLUSTER_SAVECONFIG from './CLUSTER_SAVECONFIG';
|
import CLUSTER_SAVECONFIG from './CLUSTER_SAVECONFIG';
|
||||||
@@ -437,8 +437,8 @@ export default {
|
|||||||
clusterForget: CLUSTER_FORGET,
|
clusterForget: CLUSTER_FORGET,
|
||||||
CLUSTER_GETKEYSINSLOT,
|
CLUSTER_GETKEYSINSLOT,
|
||||||
clusterGetKeysInSlot: CLUSTER_GETKEYSINSLOT,
|
clusterGetKeysInSlot: CLUSTER_GETKEYSINSLOT,
|
||||||
// CLUSTER_INFO,
|
CLUSTER_INFO,
|
||||||
// clusterInfo: CLUSTER_INFO,
|
clusterInfo: CLUSTER_INFO,
|
||||||
CLUSTER_KEYSLOT,
|
CLUSTER_KEYSLOT,
|
||||||
clusterKeySlot: CLUSTER_KEYSLOT,
|
clusterKeySlot: CLUSTER_KEYSLOT,
|
||||||
CLUSTER_LINKS,
|
CLUSTER_LINKS,
|
||||||
@@ -447,10 +447,10 @@ export default {
|
|||||||
clusterMeet: CLUSTER_MEET,
|
clusterMeet: CLUSTER_MEET,
|
||||||
CLUSTER_MYID,
|
CLUSTER_MYID,
|
||||||
clusterMyId: CLUSTER_MYID,
|
clusterMyId: CLUSTER_MYID,
|
||||||
// CLUSTER_NODES,
|
CLUSTER_NODES,
|
||||||
// clusterNodes: CLUSTER_NODES,
|
clusterNodes: CLUSTER_NODES,
|
||||||
// CLUSTER_REPLICAS,
|
CLUSTER_REPLICAS,
|
||||||
// clusterReplicas: CLUSTER_REPLICAS,
|
clusterReplicas: CLUSTER_REPLICAS,
|
||||||
CLUSTER_REPLICATE,
|
CLUSTER_REPLICATE,
|
||||||
clusterReplicate: CLUSTER_REPLICATE,
|
clusterReplicate: CLUSTER_REPLICATE,
|
||||||
CLUSTER_RESET,
|
CLUSTER_RESET,
|
||||||
|
Reference in New Issue
Block a user